/////////////////////////////////////////////////////////////////////////////////
// Paint.NET                                                                   //
// Copyright (C) dotPDN LLC, Rick Brewster, Tom Jackson, and contributors.     //
// Portions Copyright (C) Microsoft Corporation. All Rights Reserved.          //
// See src/Resources/Files/License.txt for full licensing and attribution      //
// details.                                                                    //
// .                                                                           //
/////////////////////////////////////////////////////////////////////////////////

using System;
using System.Drawing;

namespace PaintDotNet
{
    /// <summary>
    /// Histogram is used to calculate a histogram for a surface (in a selection,
    /// if desired). This can then be used to retrieve percentile, average, peak,
    /// and distribution information.
    /// </summary>
    public abstract class Histogram
    {
        protected long[][] histogram;
        public long[][] HistogramValues
        {
            get
            {
                return this.histogram;
            }

            set
            {
                if (value.Length == this.histogram.Length && value[0].Length == this.histogram[0].Length)
                {
                    this.histogram = value;
                    OnHistogramUpdated();
                }
                else
                {
                    throw new ArgumentException("value muse be an array of arrays of matching size", "value");
                }
            }
        }
     
        public int Channels
        {
            get
            {
                return this.histogram.Length;
            }
        }
     
        public int Entries
        {
            get
            {
                return this.histogram[0].Length;
            }
        }

        protected internal Histogram(int channels, int entries)
        {
            this.histogram = new long[channels][];

            for (int channel = 0; channel < channels; ++channel)
            {
                this.histogram[channel] = new long[entries];
            }
        }

        public event EventHandler HistogramChanged;
        protected void OnHistogramUpdated()
        {
            if (HistogramChanged != null)
            {
                HistogramChanged(this, EventArgs.Empty);
            }
        }

        protected ColorBgra[] visualColors;
        public ColorBgra GetVisualColor(int channel)
        {
            return visualColors[channel];
        }

        public long GetOccurrences(int channel, int val) 
        {
            return histogram[channel][val];
        }

        public long GetMax() 
        {
            long max = -1;

            foreach (long[] channelHistogram in histogram)
            {
                foreach (long i in channelHistogram)
                {
                    if (i > max)
                    {
                        max = i;
                    }
                }
            }
            
            return max;
        }

        public long GetMax(int channel)
        {
            long max = -1;

            foreach (long i in histogram[channel])
            {
                if (i > max)
                {
                    max = i;
                }
            }

            return max;
        }

        public float[] GetMean() 
        {
            float[] ret = new float[Channels];

            for (int channel = 0; channel < Channels; ++channel)
            {
                long[] channelHistogram = histogram[channel];
                long avg = 0;
                long sum = 0;

                for (int j = 0; j < channelHistogram.Length; j++)
                {
                    avg += j * channelHistogram[j];
                    sum += channelHistogram[j];
                }

                if (sum != 0)
                {
                    ret[channel] = (float)avg / (float)sum;
                }
                else
                {
                    ret[channel] = 0;
                }
            }

            return ret;
        }

        public int[] GetPercentile(float fraction) 
        {
            int[] ret = new int[Channels];

            for (int channel = 0; channel < Channels; ++channel)
            {
                long[] channelHistogram = histogram[channel];
                long integral = 0;
                long sum = 0;

                for (int j = 0; j < channelHistogram.Length; j++) 
                {
                    sum += channelHistogram[j];
                }

                for (int j = 0; j < channelHistogram.Length; j++)
                {
                    integral += channelHistogram[j];

                    if (integral > sum * fraction) 
                    {
                        ret[channel] = j;
                        break;
                    }
                }
            }

            return ret;
        }

        public abstract ColorBgra GetMeanColor();

        public abstract ColorBgra GetPercentileColor(float fraction);

        /// <summary>
        /// Sets the histogram to be all zeros.
        /// </summary>
        protected void Clear()
        {
            histogram.Initialize();
        }

        protected abstract void AddSurfaceRectangleToHistogram(Surface surface, Rectangle rect);

        public void UpdateHistogram(Surface surface)
        {
            Clear();
            AddSurfaceRectangleToHistogram(surface, surface.Bounds);
            OnHistogramUpdated();
        }

        public void UpdateHistogram(Surface surface, Rectangle rect)
        {
            Clear();
            AddSurfaceRectangleToHistogram(surface, rect);
            OnHistogramUpdated();
        }

        public void UpdateHistogram(Surface surface, PdnRegion roi)
        {
            Clear();

            foreach (Rectangle rect in roi.GetRegionScansReadOnlyInt()) 
            {
                AddSurfaceRectangleToHistogram(surface, rect);
            }

            OnHistogramUpdated();
        }
    }
}
